<template>
  <el-dialog
    append-to-body
    top="30px"
    width="800px"
    :before-close="beforeClose"
    :title="title"
    :close-on-click-modal="false"
    :visible.sync="innerVisible"
  >
    <c-form
      ref="cForm"
      v-loading="vLoading"
      v-bind="$attrs"
      :form-items="dialogFormItems"
      :label-width="labelWidth"
      :loading="saveLoading"
      @submit="handleSubmit"
      @dismiss="handleDismiss"
    >
      <slot v-for="slot in formSlots" :name="slot" :slot="slot"></slot>

      <slot></slot>
    </c-form>
  </el-dialog>
</template>
<script>
import { cloneDeep, arrayFromString } from '@vimi/utils-tool'

export default {
  name: 'CFormDialog',
  components: {},
  props: {
    vLoading: Boolean,
    formItems: {
      type: Array,
      required: true,
    },
    formData: {
      type: Object,
      required: true,
    },
    tableName: {
      type: String,
      required: true,
    },
    autoClose: {
      type: Boolean,
      default: true,
    },
    visible: Boolean,
    title: String,
    service: String,
    tableId: String,
    beforeSave: Function,
    afterSave: Function,
  },
  data() {
    return {
      innerVisible: false,
      dialogFormItems: [],
      formSlots: [],
      // save
      saveLoading: false,
      saveData: {},
      // upload
      uploadTotal: 0,
      uploadCount: 0,
      uploadKeys: {},
      uploadFiles: {},
    }
  },
  computed: {
    labelWidth() {
      return (this.$attrs && this.$attrs['label-width']) || '110px'
    },
    serviceName() {
      return this.service || this.$serviceName
    },
  },
  watch: {
    // 这里重复接收formItems，是为了在这里监听组件的自定义事件(upload submit)
    // formItems: {
    //   handler: function () { this.initFormItems() },
    //   deep: true,
    //   immediate: true
    // },

    // 当formData改变时给_fromItems赋值
    formData: {
      handler: function () {
        this.initFormItems()
      },
      deep: true,
      immediate: true,
    },
    visible(val) {
      this.innerVisible = val
      if (val && !this.tableId) {
        this.clearValidate()
      }
    },
  },
  created() {},
  mounted() {},
  methods: {
    initFormItems() {
      this.formSlots = []
      this.uploadFiles = {}
      this.dialogFormItems = this.formItems.map(item => this.computeFormItem(item))
    },

    beforeClose() {
      this.$emit('close', false)
      this.$refs.cForm.handleDismiss()
    },

    handleDismiss() {
      this.$emit('dismiss')
      this.$emit('close', false)
      this.$emit('update:visible', false)
    },

    clearValidate() {
      this.$nextTick(() => this.$refs.cForm.clearValidate())
    },

    submit() {
      this.$refs.cForm.handleSubmit()
    },

    validateField(props, callback) {
      this.$refs.cForm.validateField(props, callback)
    },

    handleSubmit(form, formItems) {
      this.handleSaveData(form, formItems)
    },

    computeFormItem(formItem) {
      const item = cloneDeep(formItem)

      // 编辑的时候，给formItems的每一项赋值，添加时初始化，不设置则用formItmes的默认值
      const { key } = item.attrs
      let val = this.formData[key]
      if (val === undefined || val === null) {
        val = formItem.attrs.value
      }

      // 监听upload上传事件
      if (item.tag === 'upload') {
        const fileList = arrayFromString(val).map(img => {
          return { url: img, name: img.replace(/.*\/(.*)$/, '$1') }
        })

        // 处理上传文件必传验证问题
        // 根据uploadFiles对应key的文件长度来判断是否有上传，这需要在上传组件选择或删除文件时去验证
        let onChange = () => {}
        let onRemove = () => {}
        const { rules } = item.itemAttrs || {}
        const ruleIndex = this.findRequiredRuleIndex(rules)
        if (ruleIndex > -1) {
          this.uploadFiles[key] = fileList.length
          const isArrayRule = Array.isArray(rules)
          let { required, message, trigger, ...rule } = isArrayRule ? rules[ruleIndex] : rules
          const triggerOnChange = `${trigger || ''}`.includes('change')
          rule = {
            ...rule,
            required,
            trigger,
            validator: (rule, value, callback) => {
              if (!this.uploadFiles[key]) {
                return callback(new Error(message))
              }
              callback()
            },
          }
          if (isArrayRule) {
            item.itemAttrs.rules[ruleIndex] = rule
          } else {
            item.itemAttrs.rules = rule
          }
          const attrOnChange = item.attrs.onChange
          onChange = (file, fileList) => {
            this.uploadFiles[key] = fileList.length
            triggerOnChange && this.validateField(key)
            attrOnChange && attrOnChange(file, fileList)
          }
          const attrOnRemove = item.attrs.onRemove
          onRemove = (file, fileList) => {
            this.uploadFiles[key] = fileList.length
            triggerOnChange && this.validateField(key)
            attrOnRemove && attrOnRemove(file, fileList)
          }
        }

        item.attrs = Object.assign({}, item.attrs, {
          fileList,
          ref: key,
          onComplete: v => this.handleImagesComplete(key, v),
          onExceed: () => this.$message.error(`图片不能超过${item.attrs.limit || 1}张！`),
          onChange,
          onRemove,
        })
        this.uploadKeys[item.attrs.ref] = item
      }

      // 插槽
      if (item.slot) {
        this.formSlots.push(item.slot)
      }
      if (item.itemSlot) {
        this.formSlots.push(item.itemSlot)
      }
      if (item.tagSlots) {
        for (let slot of item.tagSlots) {
          this.formSlots.push(slot.slot)
        }
      }

      if (item.appendSlot) {
        if (!this.formSlots.includes(item.appendSlot)) {
          this.formSlots.push(item.appendSlot)
        }
      }

      item.attrs.value = val

      return item
    },

    findRequiredRuleIndex(rules) {
      if (!rules) return -1
      if (Array.isArray(rules)) {
        return rules.findIndex(it => it.required)
      }
      return rules.required ? 0 : -1
    },

    setAttrs(key, attrs) {
      this.$nextTick(() => {
        const { cForm } = this.$refs
        cForm && cForm.setAttrs(key, attrs)
      })
    },

    handleSaveData(form, formItems) {
      const formModel = { ...form }

      this.uploadTotal = 0
      this.uploadKeys = {}
      for (let i = 0; i < this.dialogFormItems.length; i++) {
        const formItem = this.dialogFormItems[i]
        const key = formItem.attrs.key
        const value = formModel[key]
        const tag = formItem.tag

        // 过滤数值和日期型字段的空值，避免api请求出错，但要使number的0也上传
        const isEmptyNumberOrDate = !value && value !== 0 && ['number', 'date'].includes(tag)
        if (isEmptyNumberOrDate || formItem.ifDrop) {
          delete formModel[key]
        }

        // 数据库有时会存一冗余字段，不与用户交互，但会由默认值或计算得出再存数据库
        if (formItem.getValue) {
          formModel[key] = formItem.getValue(value, form)
        }

        if (tag === 'upload' && formItems[i]._ifRender) {
          this.uploadTotal++
          this.uploadKeys[formItem.attrs.ref] = formItem
        }
      }

      this.saveData = formModel

      if (this.uploadTotal) {
        this.uploadCount = 0
        Object.keys(this.uploadKeys).forEach(key => {
          this.$refs.cForm.$refs[key].submit()
        })
      } else {
        this.handleBeforeSave()
      }
    },

    handleImagesComplete(key, images) {
      this.saveData[key] = this.getImageString(images)
      this.uploadCount++
      if (this.uploadCount >= this.uploadTotal) {
        this.handleBeforeSave()
      }
    },

    getImageString(images = []) {
      return images && images.length ? `["${images.map(item => item.url).join('","')}"]` : ''
    },

    // 保存之前需要执行的动作，可以是同步或异步
    handleBeforeSave() {
      if (!this.beforeSave) {
        return this.adjustToSave({ ...this.saveData })
      }

      const bs = this.beforeSave({ ...this.saveData })
      if (bs && bs.then) {
        this.saveLoading = true
        bs.then(res => {
          this.adjustToSave(res)
        })
          .catch(err => {
            this.afterError(err)
          })
          .then(() => {
            this.saveLoading = false
          })
      } else {
        this.adjustToSave(bs)
      }
    },

    // 保存逻辑判断
    adjustToSave(form) {
      // beforeSave 必须返回提交对象或异步then返回，否则不进行保存，这也提供了组件外自己进行提交的逻辑
      if (!form) {
        if (this.autoClose) {
          this.onAfterSave()
        }
        return false
      }

      // 通过tableId的有无判断是添加还是修改
      if (this.tableId) {
        this.putData(form)
      } else {
        this.postData(form)
      }
    },

    // 添加数据
    postData(form) {
      this.saveLoading = true
      this.$api
        .addData(this.serviceName, this.tableName, form)
        .then(res => {
          this.$message.success(`添加成功！`)
          this.onAfterSave(res, form)
        })
        .catch(err => {
          this.afterError(err)
        })
        .finally(() => {
          this.saveLoading = false
        })
    },

    // 修改数据
    putData(form) {
      this.saveLoading = true
      this.$api
        .updateData(this.serviceName, this.tableName, this.tableId, form)
        .then(res => {
          this.$message.success(`修改成功！`)
          this.onAfterSave(res, form)
        })
        .catch(err => {
          this.afterError(err)
        })
        .finally(() => {
          this.saveLoading = false
        })
    },

    afterError(err) {
      if (err && err.status === 403) {
        this.$message.error('还没有给您授权，请找相关部门负责人！')
      } else if (err.data && err.data.error_description) {
        this.$message.error(err.data.error_description)
      } else {
        this.$message.error(`保存失败，请重试！（${err.status}）`)
      }
    },

    onAfterSave(res, form = {}) {
      this.$emit('success', res)

      if (this.afterSave) {
        this.afterSave(res, { ...form })
      }

      this.beforeClose()
    },
  },
}
</script>
<style scoped></style>
